.. _глава_55_start:

Наследование
=============

.. index::
    наследование
    inheritance

Наследование - это определение более специализированного типа на основе существующего более общего
базового типа. Специализированный тип приобретает члены базового типа и в результате
в результате может быть подставлен вместо базового типа.
Наследование доступно для классов, но не для структур. Класс, который наследует другой
называется подклассом, а класс, который наследуется, - суперклассом,
также называемый базовым классом.
В D существует два типа наследования. В этой главе мы рассмотрим наследование реализации
наследование реализации в этой главе и оставим наследование интерфейсов для следующей главы.
При определении подкласса суперкласс указывается после двоеточия:

.. code-block:: d

    class SubClass : SuperClass {
    // ...
    }

Чтобы увидеть пример этого, предположим, что уже существует следующий класс
который представляет часы:

.. code-block:: d

    class Clock {
        int hour;
        int minute;
        int second;

        void adjust(int hour, int minute, int second = 0) {
            this.hour = hour;
            this.minute = minute;
            this.second = second;
        }
    }

Очевидно, что членам этого класса не нужны специальные значения во время создания,
поэтому конструктор отсутствует. Вместо этого члены устанавливаются функцией
adjust():

.. code-block:: d

    auto deskClock = new Clock;
    deskClock.adjust(20, 30);
    writefln("%02s:%02s:%02s",deskClock.hour, deskClock.minute, deskClock.second);

Примечание:Было бы полезнее получить временную строку с помощью функции toString(). Это
будет добавлено позже при объяснении ключевого слова override.

Вывод:

.. code-block:: console

    20:30:00

При такой функциональности Clock может быть и структурой, и в зависимости  от потребностей программы, этого может быть достаточно.
Однако статус класса позволяет наследовать от Clock.
Чтобы увидеть пример наследования, давайте рассмотрим AlarmClock, который не только
включает в себя всю функциональность Clock, но и предоставляет способ установки
будильника. Сначала определим этот тип без учета Clock. Если бы мы это сделали, нам бы
пришлось бы включить те же три члена Clock и ту же функцию adjust()
которая их настраивает. AlarmClock также будет иметь другие члены для
дополнительной функциональности:

.. code-block:: d

    class AlarmClock
    {
        int hour;
        int minute;
        int second;
        int alarmHour;
        int alarmMinute;

        void adjust(int hour, int minute, int second = 0)
        {
            this.hour = hour;
            this.minute = minute;
            this.second = second;
        }

        void adjustAlarm(int hour, int minute)
        {
            alarmHour = hour;
            alarmMinute = minute;
        }
    }


Члены, которые появляются точно в Часах, выделены. Как можно видеть,
определение Clock и AlarmClock по отдельности приводит к дублированию кода.
Наследование помогает в таких случаях. Наследование AlarmClock от Clock
упрощает новый класс и уменьшает дублирование кода:

.. code-block:: d

    class AlarmClock : Clock
    {
        int alarmHour;
        int alarmMinute;

        void adjustAlarm(int hour, int minute)
        {
            alarmHour = hour;
            alarmMinute = minute;
        }
    }

Новое определение AlarmClock эквивалентно предыдущему. На
выделенная часть нового определения соответствует выделенным частям старого
старого определения.
Поскольку AlarmClock наследует члены Clock, его можно использовать так же, как и
Clock:

.. code-block:: d

    auto bedSideClock = new AlarmClock;
    bedSideClock.adjust(20, 30);
    bedSideClock.adjustAlarm(7, 0);

К членам, унаследованным от суперкласса, можно обращаться так же, как если бы они были
как к членам подкласса

.. code-block:: d

    writefln("%02s:%02s:%02s ♫%02s:%02s",
             bedSideClock.hour,
             bedSideClock.minute,
             bedSideClock.second,
             bedSideClock.alarmHour,
             bedSideClock.alarmMinute);

Вывод

.. code-block:: console

    20:30:00 ♫07:00

*Примечание:В данном случае более полезной будет функцияAlarmClock.toString. Она будет
определена ниже.*
Наследование, используемое в этом примере, является наследованием реализации.
Если представить память в виде ленты, идущей сверху вниз, то размещение
членов AlarmClock в памяти можно представить следующим образом
иллюстрация:

.. image:: images/55_1.png

Иллюстрация выше дает представление о том, как члены
суперкласса и подкласса могут быть объединены вместе. Фактическое расположение
членов зависит от деталей реализации используемого компилятора. Для
Например, часть, помеченная как other datatyp, обычно включает указатель на
виртуальную таблицу функций (vtbl) данного типа класса. Подробности расположения объектов
расположения объектов выходят за рамки данной книги.

Предупреждение: Наследуйте только если "is a". (если я такой же как материнский класс)
--------------------------------------------------------------------------------------

Мы видели, что наследование реализации заключается в приобретении членов.
Рассматривайте этот вид наследования только в том случае, если подтип можно рассматривать как
разновидностью супертипа, как во фразе "будильник является часами".
"Является" - не единственное отношение между типами; более распространенным отношением является
отношение "имеет". Например, предположим, что мы хотим добавить понятие
батареи в класс часов. Не следует добавлять Battery в
Clock по наследованию, потому что утверждение "часы - это батарейка" не является истинным:

.. code-block:: d

    class Clock : Battery // ←ошибочное решение
    {
    // ...
    }

Часы - это не батарейка; часы содержат батарейку. Когда существует такое отношение
сдерживания, тип, который содержится, должен быть определен как член типа
который его содержит:

.. code-block:: d

    class Clock
    {
        Battery battery; // ← правильный подход
        // ...
    }

Мнжественое наследование не поддерживается
-------------------------------------------

Классы могут наследоваться только от одного базового класса (который сам потенциально может
наследоваться от другого класса). Другими словами, множественное наследование не
поддерживается в D.
Например, если предположить, что существует также класс SoundEmitter, и даже если
даже если "будильник - это объект, издающий звук", тоже верно, невозможно
наследовать AlarmClock и от Clock, и от SoundEmitter

.. code-block:: d

    class SoundEmitter
    {
    // ...
    }

    class AlarmClock : Clock, SoundEmitter // ← ошибка компиляции
    {
    // ...
    }

С другой стороны, не существует ограничений на количество интерфейсов, от которые класс может реализовать. С ключевым словом interface мы познакомимся в одной из следующих глав.
Кроме того, не существует ограничений на глубину иерархии наследования:

.. code-block:: d

    class MusicalInstrument
    {
    // ...
    }

    class StringInstrument : MusicalInstrument
    {
    // ...
    }

    class Violin : StringInstrument
    {
    // ...
    }

Иерархические диаграммы
-----------------------

Типы, связанные отношением "is a", образуют иерархию классов.
Согласно конвенциям ООП, иерархии классов представляются следующим образом
суперклассы находятся наверху, а подклассы - внизу. На
отношения наследования обозначаются стрелками, направленными от подклассов к
суперклассам.
Например, иерархия музыкальных инструментов может быть представлена следующим образом:

.. image:: images/55.3_1.png

.. index::
    super

Доступ к членам суперкласса
---------------------------

Ключевое слово super позволяет обращаться к членам, которые наследуются от
суперкласса.

.. code-block:: d

    class AlarmClock : Clock
    {
    // ...
        void foo()
        {
            super.minute = 10; // The inherited 'minute' member
            minute = 10; // Same thing if there is no ambiguity
        }
    }

Ключевое слово super не всегда необходимо; минутка сама по себе имеет то же значение
в приведенном выше коде. Ключевое слово super необходимо, когда и суперкласс, и
подкласс имеют члены с одинаковыми именами. Мы увидим это ниже, когда нам
когда нам нужно будет написать super.reset() и super.toString().
Если несколько классов в дереве наследования определяют символ с одним и тем же именем,
можно использовать конкретное имя класса в дереве наследования, чтобы различать
между символами:

.. code-block:: d

    class Device
    {
        string manufacturer;
    }

    class Clock : Device
    {
        string manufacturer;
    }

    class AlarmClock : Clock
    {
        // ...
        void foo()
        {
            Device.manufacturer = "Sunny Horology, Inc.";
            Clock.manufacturer = "Better Watches, Ltd.";
        }
    }

Конструирование членов суперкласса
----------------------------------

Другое использование ключевого слова super - это вызов конструктора суперкласса.
Это аналогично вызову перегруженных конструкторов текущего класса: this
при вызове конструкторов текущего класса и super при вызове
при вызове конструкторов суперкласса.
Вызывать конструктор суперкласса явно не обязательно. Если конструктор
подкласса делает явный вызов любой перегрузки super, то этот
конструктор выполняется этим вызовом. В противном случае, если у суперкласса есть конструктор по умолчанию
конструктор по умолчанию, то он выполняется автоматически перед входом в тело подкласса.
Мы еще не определили конструкторы для классов Clock и AlarmClock.
По этой причине члены обоих классов инициализируются значениями .init
значениями соответствующих типов, которые для int равны 0.
Предположим, что у Clock есть следующий конструктор:

.. code-block:: d

    class Clock
    {
        this(int hour, int minute, int second)
        {
            this.hour = hour;
            this.minute = minute;
            this.second = second;
        }
        // ...
    }


Этот конструктор необходимо использовать при создании объектов Clock:

.. code-block:: d

    auto clock = new Clock(17, 15, 0);

Естественно, программисты, использующие тип Clock напрямую, должны будут использовать
этот синтаксис. Однако при конструировании объекта AlarmClock они не могут
сконструировать его часть Clock отдельно. Кроме того, пользователям AlarmClock не обязательно
даже знать, что он наследуется от Clock.
Пользователь AlarmClock должен просто сконструировать объект AlarmClock и использовать его
в программе, не обращая внимания на его наследие от Clock:

.. code-block:: d

    auto bedSideClock = new AlarmClock(/* ... */);
    // ... use as an AlarmClock ...

По этой причине конструирование части суперкласса является обязанностью
подкласса. Подкласс вызывает конструктор суперкласса с помощью функции super()
синтаксиса:

.. code-block:: d

    class AlarmClock : Clock
    {
        this(int hour, int minute, int second, // for Clock's members
            int alarmHour, int alarmMinute)
        { // for AlarmClock's members
            super(hour, minute, second);
            this.alarmHour = alarmHour;
            this.alarmMinute = alarmMinute;
        }
        // ...
    }


Конструктор AlarmClock принимает аргументы как для своих собственных членов, так и для
членов своего суперкласса. Затем он использует часть этих аргументов для построения
часть своего суперкласса

Переопределение определений функций-членов
-------------------------------------------

Одним из преимуществ наследования является возможность переопределять функции-члены
суперкласса в подклассе. Это называется переопределением: существующее определение
функции-члена суперкласса переопределяется подклассом с помощью ключевого слова
ключевого слова override.
Переопределяемые функции называются виртуальными. Виртуальные функции
реализуются компилятором с помощью таблиц указателей виртуальных функций (vtbl) и указателей vtbl
указателей. Детали этого механизма выходят за рамки данной книги.
Однако каждый системный программист должен знать, что вызов виртуальных функций
обходятся дороже, чем вызовы обычных функций. Каждый не приватный класс
функция-член в D по умолчанию является виртуальной. По этой причине, когда суперкласс
функция суперкласса не нуждается в переопределении, она должна быть определена как final, чтобы
чтобы она не была виртуальной. С ключевым словом final мы познакомимся позже в главе Интерфейсы (:ref:`Интерфейсы<Интерфейсы>`).
Предположим, что у Clock есть функция-член, которая используется для сброса его
все свои члены в ноль:

.. code-block:: d

    class Clock
    {
        void reset()
        {
            hour = 0;
            minute = 0;
            second = 0;
        }
        // ...
    }

That function is inherited by AlarmClock and can be called on an AlarmClock
object:

.. code-block:: d

    auto bedSideClock = new AlarmClock(20, 30, 0, 7, 0);
    // ...
    bedSideClock.reset();

Однако, обязательно не зная о членах AlarmClock, Clock.reset может
сбросить только свои собственные члены. По этой причине, чтобы сбросить члены подкласса
необходимо переопределить функцию reset():

.. code-block:: d

    class AlarmClock : Clock
    {
        override void reset()
        {
            super.reset();
            alarmHour = 0;
            alarmMinute = 0;
        }
        // ...
    }

Подкласс сбрасывает только свои собственные члены и передает остальную часть задачи в
Clock вызовом super.reset(). Обратите внимание, что написать просто reset() не получится.
так как это вызовет функцию reset() самого AlarmClock. Вызов функции reset() из
внутри себя приведет к бесконечной рекурсии.
Причина, по которой я отложил определение функции toString() до этого момента, заключается в том.
что она должна быть определена с помощью ключевого слова override для классов. Как мы увидим в
следующей главе, каждый класс автоматически наследуется от суперкласса под названием
Object, а в Object уже определена функция-член toString().
По этой причине функция-член toString() для классов должна быть определена
с помощью ключевого слова override:

.. code-block:: d

    import std.string;

    class Clock
    {
        override string toString() const
        {
            return format("%02s:%02s:%02s", hour, minute, second);
        }
        // ...
    }

    class AlarmClock : Clock
    {
        override string toString() const
        {
            return format("%s ♫%02s:%02s", super.toString(), alarmHour, alarmMinute);
        }
        // ...
    }

Обратите внимание, что AlarmClock снова отправляет часть задачи в Clock с помощью вызова
super.toString().
Эти две переопределения toString() позволяют преобразовывать объекты AlarmClock в
строки:

.. code-block:: d

    void main()
    {
        auto deskClock = new AlarmClock(10, 15, 0, 6, 45);
        writeln(deskClock);
    }

Вывод:

.. code-block:: console

    10:15:00 ♫06:45

Использование подкласса вместо суперкласса
-------------------------------------------

Поскольку суперкласс является более общим, а подкласс - более специализированным, объекты
подкласса могут использоваться там, где требуется объект типа суперкласса.
Это называется полиморфизмом.
Концепции общих и специализированных типов можно увидеть в утверждениях типа
"этот тип относится к тому типу": "будильник - это часы", "студент - это человек", "кошка - это
животное" и т. д. Соответственно, будильник может быть использован там, где нужны часы, студент - там, где нужен человек.
студент может быть использован там, где нужен человек, а кошка может быть использована там, где нужно животное.
животное.
Когда объект подкласса используется в качестве объекта суперкласса, он не теряет свой
свой специализированный тип. Это похоже на примеры из реальной жизни: Использование будильника
в качестве часов не меняет того факта, что это будильник; он
он по-прежнему будет вести себя как будильник.
Предположим, что функция принимает в качестве параметра объект Clock, который она сбрасывает
в какой-то момент во время выполнения:

.. code-block:: d

    void use(Clock clock)
    {
    // ...
        clock.reset();
    // ...
    }

Полиморфизм позволяет передать AlarmClock в такую функцию:

.. code-block:: d

    auto deskClock = new AlarmClock(10, 15, 0, 6, 45);
    writeln("Before: ", deskClock);
    use(deskClock);
    writeln("After : ", deskClock)

Это соответствует соотношению "будильник - это часы". В результате
члены объекта deskClock обнуляются:

.. code-block:: console

    До: 10:15:00 ♫06:45
    После: 00:00:00 ♫00:00

Важным замечанием здесь является то, что не только члены Clock, но и
члены AlarmClock были сброшены.
Хотя функция use() вызывает функцию reset() для объекта Clock, поскольку реальным объектом является
AlarmClock, функция, которая вызывается, - AlarmClock.reset. Согласно ее
определению, приведенному выше, AlarmClock.reset сбрасывает члены как Clock, так и
AlarmClock.
Другими словами, хотя функция use() использует объект как Clock, фактический объект
может быть унаследованным типом, который ведет себя по-своему.
Давайте добавим еще один класс в иерархию Clock. Функция reset() этого
устанавливает его члены в случайные значения

.. code-block:: d

    import std.random;

    class BrokenClock : Clock
    {
        this()
        {
            super(0, 0, 0);
        }

        override void reset()
        {
            hour = uniform(0, 24);
            minute = uniform(0, 60);
            second = uniform(0, 60);
        }
    }

Когда объект BrokenClock отправляется в use(), вызывается специальная функция reset()
будет вызвана специальная функция BrokenClock. Опять же, хотя он передается как Clock,
фактический объект по-прежнему является BrokenClock:

.. code-block:: d

    auto shelfClock = new BrokenClock;
    use(shelfClock);
    writeln(shelfClock);

В результате сброса BrokenClock выводятся случайные значения времени:

.. code-block:: d

    22:46:37

Наследование является транзитивным
-----------------------------------

Полиморфизм не ограничивается двумя классами. Подклассы подклассов также могут
быть использованы вместо любого суперкласса в иерархии.
Рассмотрим иерархию MusicalInstrument:

.. code-block:: d

    class MusicalInstrument
    {
    // ...
    }

    class StringInstrument : MusicalInstrument
    {
    // ...
    }

    class Violin : StringInstrument
    {
    // ...
    }

Приведенное выше наследование строит следующие отношения: "струнный инструмент - это
музыкальный инструмент" и "скрипка - струнный инструмент". Следовательно, верно и то, что
что "скрипка - это музыкальный инструмент". Следовательно, объект Violin может быть использован
вместо MusicalInstrument.
Предположим, что весь вспомогательный код ниже также определен:


.. code-block:: d

    void playInTune(MusicalInstrument instrument,   MusicalPiece piece)
    {
        instrument.tune();
        instrument.play(piece);
    }

    // ...
    auto myViolin = new Violin;
    playInTune(myViolin, improvisation);

Хотя функция playInTune() ожидает MusicalInstrument, она вызывается с
Violin из-за отношения "скрипка - музыкальный инструмент".
Наследование может быть настолько глубоким, насколько это необходимо

Абстрактные функции-члены и абстрактные классы
----------------------------------------------

Иногда встречаются функции-члены, которые естественным образом появляются в  интерфейсе класса, даже если этот класс не может предоставить их определение. Когда не существует
конкретного определения функции-члена, эта функция является абстрактным членом класса
Класс, в котором есть хотя бы одна абстрактная функция-член, является абстрактным классом.
Например, суперкласс ChessPiece в иерархии может иметь функцию-член
isValid(), которая определяет, является ли данный ход действительным для
данной шахматной фигуры. Поскольку действительность хода зависит от фактического типа шахматной фигуры
шахматной фигуры, общий класс ChessPiece не может самостоятельно принимать такое решение. Действительные
ходы могут быть известны только подклассам, таким как пешка, король и т.д.
Ключевое слово abstract указывает, что наследуемый класс должен реализовать такой
метод самостоятельно:


.. code-block:: d

    class ChessPiece
    {
        abstract bool isValid(Square from, Square to);
    }

Невозможно построить объекты абстрактных классов:

.. code-block:: d

auto piece = new ChessPiece; // ← ОШИБКА компиляции

Подкласс должен переопределить и реализовать все абстрактные функции, чтобы
чтобы сделать класс неабстрактным и, следовательно, конструируемым:

.. code-block:: d

    class Pawn : ChessPiece
    {
        override bool isValid(Square from, Square to)
        {
        // ... реализация isValid для пешки ...return decision;
        }
    }

Теперь можно конструировать объекты Pawn:

.. code-block:: d

    auto piece = new Pawn; // компилируется

Обратите внимание, что абстрактная функция может иметь собственную реализацию, но это
все равно потребуется, чтобы подкласс предоставил свою собственную реализацию такой функции функции. Например, реализация ChessPiece может предоставлять некоторые
полезные проверки:

.. code-block:: d

    class ChessPiece
    {
        abstract bool isValid(Square from, Square to)
        {
        // We require the 'to' position to be different than
        // the 'from' position
            return from != to;
        }
    }

    class Pawn : ChessPiece
    {
        override bool isValid(Square from, Square to)
        {
        // First verify if it is a valid move for any ChessPiece
            if (!super.isValid(from, to))
            {
                return false;
            }

            // ... then check if it is valid for the Pawn ...
            return decision;
        }
    }

Класс ChessPiece по-прежнему является абстрактным, несмотря на то, что функция isValid() уже была реализована.
реализован, но класс Pawn не является абстрактным и может быть инстанцирован.

Примеры
-------

Рассмотрим иерархию классов, представляющих железнодорожный транспорт:

.. image:: images/55.10_1.png

Функции, которые RailwayCar объявит абстрактными, обозначены
вопросительными знаками.
Поскольку моей целью является только представление иерархии классов и указание на некоторые из ее
я не буду полностью реализовывать эти классы. Вместо того чтобы выполнять реальную
работы, они будут просто печатать сообщения.
Самым общим классом в приведенной выше иерархии является RailwayVehicle. В этой
программе он будет знать только, как перемещать себя

.. code-block:: d

    class RailwayVehicle
    {
        void advance(size_t kilometers)
        {
            writefln("The vehicle is advancing %s kilometers",kilometers);
        }
    }

Классом, наследующим от RailwayVehicle, является Locomotive, у которого пока нет
никаких специальных членов:

.. code-block:: d

    class Locomotive : RailwayVehicle
    {
    }

Мы добавим специальную функцию-член makeSound() в Locomotive позже во время
во время одного из упражнений.
RailwayCar также является RailwayVehicle. Однако если иерархия поддерживает
различные типы железнодорожных вагонов, то определенные действия, такие как погрузка и разгрузка
должны выполняться в соответствии с их типами. По этой причине RailwayCar может
объявить эти две функции только как абстрактные:


.. code-block:: d

    class RailwayCar : RailwayVehicle
    {
        abstract void load();
        abstract void unload();
    }

Погрузить и разгрузить пассажирский вагон можно просто открыв двери.
В то время как для погрузки и разгрузки грузового вагона могут потребоваться носильщики и лебедки.
Следующие подклассы предоставляют определения для абстрактных функций
RailwayCar:

.. code-block::d

    class PassengerCar : RailwayCar
    {
        override void load()
        {
            writeln("The passengers are getting on");
        }

        override void unload()
        {
            writeln("The passengers are getting off");
        }
    }

    class FreightCar : RailwayCar
    {
        override void load()
        {
            writeln("The crates are being loaded");
        }

        override void unload()
        {
            writeln("The crates are being unloaded");
        }
    }

То, что класс является абстрактным, не исключает использования RailwayCar в программе.
Объекты RailwayCar не могут быть построены, но RailwayCar может быть использован в качестве
интерфейса. Поскольку подклассы определяют два отношения "пассажирский вагон - это железнодорожный вагон и "грузовой вагон - это железнодорожный вагон", объекты PassengerCar и
FreightCar могут быть использованы вместо RailwayCar. Это будет видно на примере класса Train
ниже.
Класс, представляющий поезд, может состоять из локомотива и массива
железнодорожных вагонов:

.. code-block:: d

    class Train : RailwayVehicle
    {
        Locomotive locomotive;
        RailwayCar[] cars;
        // ...
    }

Я хотел бы повторить один важный момент: Хотя и Locomotive, и
RailwayCar наследуются от RailwayVehicle, было бы неправильно наследовать
Train от любого из них. Наследование моделирует отношения "является", а поезд
не является ни локомотивом, ни пассажирским вагоном. Поезд состоит из них.
Если мы требуем, чтобы в каждом поезде был локомотив, конструктор Train
должен гарантировать, что он принимает действительный объект Locomotive. Аналогично, если железнодорожные вагоны
необязательны, они могут быть добавлены функцией-членом:

.. code-block:: d

   import std.exception;

    // ...
    class Train : RailwayVehicle
    {
        // ...
        this(Locomotive locomotive)
        {
            enforce(locomotive !is null, "Locomotive cannot be null");
            this.locomotive = locomotive;
        }

        void addCar(RailwayCar[] cars...)
        {
            this.cars ~= cars;
        }
        // ...
    }


Обратите внимание, что функция addCar() может проверять и объекты RailwayCar. Я игнорирую
эту проверку.
Мы можем представить, что отправление и прибытие поездов также должно поддерживаться
поддерживаться:

.. code-block:: d

    class Train : RailwayVehicle
    {
        // ...
        void departStation(string station)
        {
            foreach (car; cars)
            {
                car.load();
            }
            writefln("Departing from %s station", station);
        }

        void arriveStation(string station)
        {
            writefln("Arriving at %s station", station);
            foreach (car; cars)
            {
                car.unload();
            }
        }
    }

Следующая функция main() использует иерархию RailwayVehicle:

.. code-block:: d

    import std.stdio;
    // ...
    void main()
    {
        auto locomotive = new Locomotive;
        auto train = new Train(locomotive);
        train.addCar(new PassengerCar, new FreightCar);
        train.departStation("Ankara");
        train.advance(500);
        train.arriveStation("Haydarpaşa");
    }

Класс Train используется функциями, которые предоставляются двумя отдельными
интерфейсами:

#. Когда вызывается функция advance(), объект Train используется как
   RailwayVehicle, потому что эта функция объявлена RailwayVehicle.
#. Когда вызываются функции departStation() и arriveStation(),
   поезд используется как Train, потому что эти функции объявлены Train.
   Стрелки указывают, что функции load() и unload() работают в соответствии с
   фактическим типом RailwayCar:

.. code-block:: console

    The passengers are getting on ←
    The crates are being loaded ←
    Departing from Ankara station
    The vehicle is advancing 500 kilometers
    Arriving at Haydarpaşa station
    The passengers are getting off ←
    The crates are being unloaded ←

Резюме
-------
* Наследование используется для отношений "является".
* Каждый класс может наследовать не более чем от одного класса.
* super имеет два назначения: вызов конструктора суперкласса и доступ к
  к членам суперкласса.
* override служит для переопределения функций-членов суперкласса специально для
  подкласса.
* abstract требует, чтобы функция-член была переопределена.


Упражнения
----------
1. Давайте модифицируем RailwayVehicle. В дополнение к сообщению о расстоянии, которое он
он также должен издавать звуки. Чтобы вывод был коротким, выведем
звуки на 100 километров:

.. code-block:: d

    class RailwayVehicle
    {
        void advance(size_t kilometers)
        {
            writefln("The vehicle is advancing %s kilometers", kilometers);
            foreach (i; 0 .. kilometers / 100)
            {
                writefln("  %s", makeSound());
            }
        }
        // ...
    }


Однако makeSound() не может быть определена для RailwayVehicle, потому что
транспортные средства могут иметь разные звуки:
* "чух-чух" для Locomotive
* "клак-клак" для вагона.
*Примечание:Оставьте функциюTrain.makeSound для следующего упражнения.
Поскольку он должен быть переопределен, makeSound() должен быть объявлен как абстрактный
суперклассом:*

.. code-block:: d

    class RailwayVehicle
    {
    // ...
        abstract string makeSound();
    }

Реализуйте makeSound() для подклассов и попробуйте код со следующей функцией main():

.. code-block:: d

    void main()
    {
        auto railwayCar1 = new PassengerCar;
        railwayCar1.advance(100);
        auto railwayCar2 = new FreightCar;
        railwayCar2.advance(200);
        auto locomotive = new Locomotive;
        locomotive.advance(300);
    }

Заставьте программу выдать следующий результат:

.. code-block:: console

    The vehicle is advancing 100 kilometers
    clack clack
    The vehicle is advancing 200 kilometers
    clack clack
    clack clack
    The vehicle is advancing 300 kilometers
    choo choo
    choo choo
    choo choo

Обратите внимание, что нет требования, чтобы звуки PassengerCar и FreightCar были разными. Они могут использовать одну и ту же реализацию изRailwayCar.

#. Подумайте, как можно реализовать makeSound() для Train. Одна из идей заключается в том.
   что Train.makeSound может возвращать строку, состоящую из звуковчленов Train.

Решения приведены на странице ???.
